{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Teil 4: Federated Learning mit mitteln der Modele\n",
    "\n",
    "**Rückblick:** In Teil 2 dieses Tutorials wurde ein Model mit sehr simplem Federated Learning traininert. Es war dabei notwendig, dass jeder Daten-Besitzer dem Model-Besitzer seine persönlichen Gradienten anvertraute.\n",
    "\n",
    "**Beschreibung:** In diesem Tutorial werden die fortgeschrittenen Werkzeuge aus Teil 3 genutzt, um mit ihnen die Gradienten bei einem vertrauenswürdigen \"sicheren Helfer\" zu sammeln, bevor das finale Model zum Model-Besitzer zurück gesendet wird.\n",
    "\n",
    "Auf diese Weise kann nur der sichere Helfer sehen, wer welche Gradienten geschickt hat. Der Model-Besitzer kann anschließend nur erkennen wie sich das Model verändert hat, dies jedoch NICHT auf einen der Helfer (Alice oder Bob) zurückführen. Dies sorgt für grundlegende Privatsphäre. \n",
    "\n",
    "Autoren:\n",
    " - Andrew Trask - Twitter: [@iamtrask](https://twitter.com/iamtrask)\n",
    " - Jason Mancuso - Twitter: [@jvmancuso](https://twitter.com/jvmancuso)\n",
    " \n",
    "Übersetzer:\n",
    "- Jan Moritz Behnken - Github: [@JMBehnken](https://github.com/JMBehnken)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-02T19:15:24.442518Z",
     "start_time": "2020-04-02T19:15:22.376254Z"
    }
   },
   "outputs": [],
   "source": [
    "import torch\n",
    "import syft as sy\n",
    "import copy\n",
    "hook = sy.TorchHook(torch)\n",
    "from torch import nn, optim"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Schritt 1: Erstellen der Daten Besitzer\n",
    "\n",
    "Zuerst werden zwei Daten-Besitzer (Bob und Alice) erstellt, welche jeweils einen kleinen Anteil der Daten erhalten. Außerden wird eine sichere Maschine (`secure_worker`) eingesetzt. In der Praxis kann dies sichere Hardware (wie Intels SGX) oder ein vertrauenswürdiger Vermittler sein."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-02T19:19:11.966320Z",
     "start_time": "2020-04-02T19:19:11.940342Z"
    }
   },
   "outputs": [],
   "source": [
    "# create a couple workers\n",
    "bob = sy.VirtualWorker(hook, id=\"bob\")\n",
    "alice = sy.VirtualWorker(hook, id=\"alice\")\n",
    "secure_worker = sy.VirtualWorker(hook, id=\"secure_worker\")\n",
    "\n",
    "\n",
    "# A Toy Dataset\n",
    "data = torch.tensor([[0,0],[0,1],[1,0],[1,1.]], requires_grad=True)\n",
    "target = torch.tensor([[0],[0],[1],[1.]], requires_grad=True)\n",
    "\n",
    "# get pointers to training data on each worker by\n",
    "# sending some training data to bob and alice\n",
    "bobs_data = data[0:2].send(bob)\n",
    "bobs_target = target[0:2].send(bob)\n",
    "\n",
    "alices_data = data[2:].send(alice)\n",
    "alices_target = target[2:].send(alice)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Schritt 2: Erstellen des Models\n",
    "\n",
    "In diesem Beispiel wird ein simples lineares Model trainiert. Es kann normal mit PyTorchs `nn.Linear` initialisiert werden."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-02T19:20:37.213217Z",
     "start_time": "2020-04-02T19:20:37.207219Z"
    }
   },
   "outputs": [],
   "source": [
    "# Iniitalize A Toy Model\n",
    "model = nn.Linear(2,1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Schritt 3: Senden eine Kopie des Models zu Alice und Bob\n",
    "\n",
    "Als nächstes muss eine Kopie des aktuellen Models an Alice und Bob gesendet werden. Somit können beide für sich das Model auf ihre eigenen Daten anpassen."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-02T19:22:11.653780Z",
     "start_time": "2020-04-02T19:22:11.601467Z"
    }
   },
   "outputs": [],
   "source": [
    "bobs_model = model.copy().send(bob)\n",
    "alices_model = model.copy().send(alice)\n",
    "\n",
    "bobs_opt = optim.SGD(params=bobs_model.parameters(),lr=0.1)\n",
    "alices_opt = optim.SGD(params=alices_model.parameters(),lr=0.1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Schritt 4: Trainieren von Bobs und Alices Modelen (parallel)\n",
    "\n",
    "Beim Federated Learning mit Secure Averaging ist es üblich, dass jeder Daten-Besitzer sein Model erst für mehrere Iterationen auf eigenen Daten trainiert, bevor die Gewichte der einzelnen Modele gemittelt werden."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-02T19:25:12.885467Z",
     "start_time": "2020-04-02T19:25:12.794414Z"
    }
   },
   "outputs": [],
   "source": [
    "for i in range(10):\n",
    "\n",
    "    # Train Bob's Model\n",
    "    bobs_opt.zero_grad()\n",
    "    bobs_pred = bobs_model(bobs_data)\n",
    "    bobs_loss = ((bobs_pred - bobs_target)**2).sum()\n",
    "    bobs_loss.backward()\n",
    "\n",
    "    bobs_opt.step()\n",
    "    bobs_loss = bobs_loss.get().data\n",
    "\n",
    "    # Train Alice's Model\n",
    "    alices_opt.zero_grad()\n",
    "    alices_pred = alices_model(alices_data)\n",
    "    alices_loss = ((alices_pred - alices_target)**2).sum()\n",
    "    alices_loss.backward()\n",
    "\n",
    "    alices_opt.step()\n",
    "    alices_loss = alices_loss.get().data\n",
    "    \n",
    "    print(\"Bob:\" + str(bobs_loss) + \" Alice:\" + str(alices_loss))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Schritt 5: Senden beider trainierten Modele zum sicheren Helfer\n",
    "\n",
    "Da nun alle Daten-Besitzer ein eigenes teiltrainiertes Model besitzen, ist es an der Zeit diese auf eine sichere Weise zusammenzuführen. Dies wird erreicht, indem Alice und Bob ihre Modele zum (vertrauenswürdigen) sicheren Server senden.\n",
    "\n",
    "Es ist anzumerken, dass mit dieser API jedes der Modele DIREKT zum `secure_worker` gesendet wird und der Model-Besitzer die einzelnen Modele nie zu Gesicht bekommt."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-02T19:30:53.593453Z",
     "start_time": "2020-04-02T19:30:53.580536Z"
    }
   },
   "outputs": [],
   "source": [
    "alices_model.move(secure_worker)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-02T19:30:54.012408Z",
     "start_time": "2020-04-02T19:30:54.004538Z"
    }
   },
   "outputs": [],
   "source": [
    "bobs_model.move(secure_worker)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Schritt 6: Mitteln der Modele"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Der letzte Schritt dieser Trainings-Epoche ist die trainierten Modele von Bob und Alice zu mitteln und die so erhaltenen Gewichte für das globale Model zu verwenden."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-02T19:33:26.101191Z",
     "start_time": "2020-04-02T19:33:26.087096Z"
    }
   },
   "outputs": [],
   "source": [
    "with torch.no_grad():\n",
    "    model.weight.set_(((alices_model.weight.data + bobs_model.weight.data) / 2).get())\n",
    "    model.bias.set_(((alices_model.bias.data + bobs_model.bias.data) / 2).get())\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Endlos wiederholen\n",
    "\n",
    "Nun muss dies nur noch mehrere Male wiederholt werden!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-02T19:34:44.297598Z",
     "start_time": "2020-04-02T19:34:43.936070Z"
    }
   },
   "outputs": [],
   "source": [
    "iterations = 10\n",
    "worker_iters = 5\n",
    "\n",
    "for a_iter in range(iterations):\n",
    "    \n",
    "    bobs_model = model.copy().send(bob)\n",
    "    alices_model = model.copy().send(alice)\n",
    "\n",
    "    bobs_opt = optim.SGD(params=bobs_model.parameters(),lr=0.1)\n",
    "    alices_opt = optim.SGD(params=alices_model.parameters(),lr=0.1)\n",
    "\n",
    "    for wi in range(worker_iters):\n",
    "\n",
    "        # Train Bob's Model\n",
    "        bobs_opt.zero_grad()\n",
    "        bobs_pred = bobs_model(bobs_data)\n",
    "        bobs_loss = ((bobs_pred - bobs_target)**2).sum()\n",
    "        bobs_loss.backward()\n",
    "\n",
    "        bobs_opt.step()\n",
    "        bobs_loss = bobs_loss.get().data\n",
    "\n",
    "        # Train Alice's Model\n",
    "        alices_opt.zero_grad()\n",
    "        alices_pred = alices_model(alices_data)\n",
    "        alices_loss = ((alices_pred - alices_target)**2).sum()\n",
    "        alices_loss.backward()\n",
    "\n",
    "        alices_opt.step()\n",
    "        alices_loss = alices_loss.get().data\n",
    "    \n",
    "    alices_model.move(secure_worker)\n",
    "    bobs_model.move(secure_worker)\n",
    "    with torch.no_grad():\n",
    "        model.weight.set_(((alices_model.weight.data + bobs_model.weight.data) / 2).get())\n",
    "        model.bias.set_(((alices_model.bias.data + bobs_model.bias.data) / 2).get())\n",
    "    \n",
    "    print(\"Bob:\" + str(bobs_loss) + \" Alice:\" + str(alices_loss))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Zu guter Letzt muss noch sicher gestellt werden, dass das finale Model korrekt gelernt hat. Deshalb wird es auf einem Test-Datensatz evaluiert. In diesem Fall wird dafür der Original-Datensatz verwendet. In der Praxis sollte jedoch ein neuer Datensatz genutzt werden, um zu prüfen wie gut sich das Model auf Ungesehenes generalisieren lässt."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-02T19:38:23.915851Z",
     "start_time": "2020-04-02T19:38:23.913057Z"
    }
   },
   "outputs": [],
   "source": [
    "preds = model(data)\n",
    "loss = ((preds - target) ** 2).sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-02T19:38:24.476162Z",
     "start_time": "2020-04-02T19:38:24.467583Z"
    }
   },
   "outputs": [],
   "source": [
    "print(preds)\n",
    "print(target)\n",
    "print(loss.data)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In diesem kleinen Beispiel weist das gemittelte Model eine zu kleine Anpassung, verglichen mit dem lokal auf Rohdaten trainierten Model, auf. Jedoch konnte das Model trainiert werden ohne dabei die Daten des Einzelnen preiszugeben.  \n",
    "Auch war es möglich die Model-Updates von den einzelnen Helfern auf einem vertrauenswürdigen Helfer zu sammeln und somit den Verlust der Privatsphäre an den Model-Besitzer zu verhinden.\n",
    "\n",
    "In einem noch kommenden Tutorial wird es darum gehen mit dem vertrauenswürdigen Mitteln die Gradienten selbst zu nutzen und dann mit diesen besseren Gradienten zu einem stabileren Model zu gelangen."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Herzlichen Glückwunsch!!! - Zeit, der Community beizutreten! \n",
    "\n",
    "Herzlichen Glückwunsch zum Abschluss dieses Notebook-Tutorials! Wenn es Ihnen gefallen hat und Sie sich der Bewegung zur Wahrung der Privatsphäre, zum dezentralisiertenen Besitz von KI und der KI-Lieferkette (Daten) anschließen möchten, können Sie dies auf folgende Weise tun! \n",
    "\n",
    "### PySyft auf GitHub einen Stern geben! \n",
    "\n",
    "Der einfachste Weg, unserer Community zu helfen, besteht darin, die GitHub-Repos mit Sternen auszuzeichnen! Dies hilft, das Bewusstsein für die coolen Tools zu schärfen, die wir bauen. \n",
    "\n",
    "- [Gib PySyft einen Stern](https://github.com/OpenMined/PySyft)\n",
    "\n",
    "### Mach mit bei Slack! \n",
    "\n",
    "Der beste Weg, um über die neuesten Entwicklungen auf dem Laufenden zu bleiben, ist, sich unserer Community anzuschließen! Sie können dies tun, indem Sie das Formular unter [http://slack.openmined.org](http://slack.openmined.org) ausfüllen.\n",
    "\n",
    "### Treten Sie einem Code-Projekt bei! \n",
    "\n",
    "Der beste Weg, um zu unserer Community beizutragen, besteht darin, Entwickler zu werden! Sie können jederzeit zur PySyft GitHub Issues-Seite gehen und nach \"Projects\" filtern. Dies zeigt Ihnen alle Top-Level-Tickets und gibt einen Überblick darüber, an welchen Projekten Sie teilnehmen können! Wenn Sie nicht an einem Projekt teilnehmen möchten, aber ein wenig programmieren möchten, können Sie auch nach weiteren \"einmaligen\" Miniprojekten suchen, indem Sie nach GitHub-Problemen suchen, die als \"good first issue\" gekennzeichnet sind. \n",
    "\n",
    "- [PySyft Projects](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3AProject)\n",
    "- [Good First Issue Tickets](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)\n",
    "\n",
    "### Spenden\n",
    "\n",
    "Wenn Sie keine Zeit haben, zu unserer Codebase beizutragen, aber dennoch Unterstützung leisten möchten, können Sie auch Unterstützer unseres Open Collective werden. Alle Spenden fließen in unser Webhosting und andere Community-Ausgaben wie Hackathons und Meetups! \n",
    "\n",
    " - [OpenMined's Open Collective Page](https://opencollective.com/openmined)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
